﻿namespace Blip.API
{
    using System;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Collections.Generic;
    using System.Threading;
    using Utility;
    using JSONParser;
    using Tools;

    /// <summary>
    /// Klasa realizująca żądania HTTP do serwisu Blip.
    /// </summary>
    /// <seealso cref="IBlipCaller"/>
    public class BlipCaller : IBlipCaller
    {
        /// <summary>
        /// Wersja API.
        /// </summary>
        protected const string API_VERSION = "0.02";

        private const int BUFF_SIZE = 4096;

        /// <summary>
        /// Tworzy nowy wątek i wykonuje w nim delegat [action].
        /// </summary>
        /// <param name="action">Delegat wywoływany w nowym wątku</param>
        /// <returns>Prawda, gdy wątek został utworzony</returns>
        protected virtual bool Execute(Action action)
        {
            try
            {
                Thread thread = new Thread(new ThreadStart(action));
                thread.Start();
            }
            catch (Exception e)
            {
                ErrorLog.Report("ThreadException: " + e.Message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Tworzy żądanie HTTP.
        /// </summary>
        /// <param name="url">Adres żądania</param>
        /// <param name="credentials">Obiekt zawierający informacje potrzebne do uwierzytelnienia</param>
        /// <param name="method">Typ wyliczeniowy określający rodzaj żądania</param>
        /// <returns>Utworzone żądanie HTTP</returns>
        /// <seealso cref="IAuthInfo"/>
        /// <seealso cref="METHOD"/>
        protected virtual HttpWebRequest CreateRequest(Uri url, IAuthInfo credentials, METHOD method)
        {
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.Accept = "application/json";
            request.Headers.Add("X-Blip-api", API_VERSION);

            if (credentials != null)
                request.Headers.Add("Authorization", "Basic " + credentials.Credentials);

            string methodString = string.Empty;

            switch (method)
            {
                case METHOD.GET: methodString = "GET"; break;
                case METHOD.POST: methodString = "POST"; break;
                case METHOD.PUT: methodString = "PUT"; break;
                case METHOD.DELETE: methodString = "DELETE"; break;
            }
            
            request.Method = methodString;

            return request;
        }

        /// <summary>
        /// Odczytuje odpowiedź żądania HTTP, parsuje otrzymany łańcuch znakowy do formatu JSON i przesyła wynik do delegata callback.
        /// </summary>
        /// <param name="request">Żądanie HTTP, z którego odczytana zostanie odpowiedź serwera</param>
        /// <param name="callback">Delegat, do którego zostanie przesłana odpowiedź</param>
        /// <seealso cref="BlipResponse"/>
        protected virtual void ReadResponse(HttpWebRequest request, BlipResponse callback)
        {
            try
            {
                HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                using (Stream stream = response.GetResponseStream())
                {
                    string answer = new StreamReader(stream).ReadToEnd();
                    if (callback != null)
                        callback(answer.ToJSON());
                }
                response.Close();
            }
            catch (WebException e)
            {
                HttpWebResponse response = e.Response as HttpWebResponse;
                if (response != null)
                    ErrorLog.Report("WebException: Server Response " + response.StatusCode);

                if (callback != null)
                    callback(new BlipError(e.Message));
            }
            catch (Exception e)
            {
                ErrorLog.Report("Exception: Server Response " + e.Message);
                if (callback != null)
                    callback(new BlipError(e.Message));
            }
        }
        
        /// <summary>
        /// Zamienia listę par (klucz, wartość) do formatu wymaganego przy żądaniach typu HTTP, klucz1=wartość1&klucz2=wartość2 .
        /// </summary>
        /// <param name="data">Lista par (klucz, wartość)</param>
        /// <returns>Sformatowany łańcuch</returns>
        protected virtual string EncodeParams(IDictionary<string, string> data)
        {
            StringBuilder param = new StringBuilder();
            foreach (var pair in data)
                param.AppendFormat("&{0}={1}", pair.Key, pair.Value.EncodeUrl());

            param.Remove(0, 1);
            return param.ToString();
        }        

        public virtual bool Get(BlipResponse callback, Uri url, IAuthInfo credentials)
        {
            return Execute(delegate
            {
                HttpWebRequest request = CreateRequest(url, credentials, METHOD.GET);
                ReadResponse(request, callback);
            });
        }

        public virtual bool Post(BlipResponse callback, Uri url, IAuthInfo credentials, IDictionary<string, string> data)
        {
            return Execute(delegate
            {
                HttpWebRequest request = CreateRequest(url, credentials, METHOD.POST);
                string param = EncodeParams(data);

                byte[] buffer = Encoding.UTF8.GetBytes(param);

                request.ContentType = "multipart/form-data";
                request.ContentLength = buffer.Length;

                using (Stream stream = request.GetRequestStream())
                {
                    stream.Write(buffer, 0, buffer.Length);
                }

                ReadResponse(request, callback);
            });
        }

        public virtual bool Put(BlipResponse callback, Uri url, IAuthInfo credentials, IDictionary<string, string> data)
        {
            return Execute(delegate
            {
                HttpWebRequest request = CreateRequest(url, credentials, METHOD.PUT);
                string param = EncodeParams(data);

                byte[] buffer = Encoding.UTF8.GetBytes(param);

                request.ContentType = "multipart/form-data";
                request.ContentLength = buffer.Length;

                using (Stream stream = request.GetRequestStream())
                {
                    stream.Write(buffer, 0, buffer.Length);
                }

                ReadResponse(request, callback);
            });
        }

        public virtual bool Delete(BlipResponse callback, Uri url, IAuthInfo credentials)
        {
            return Execute(delegate
            {
                HttpWebRequest request = CreateRequest(url, credentials, METHOD.DELETE);
                ReadResponse(request, callback);
            });
        }

        public virtual bool Upload(BlipResponse callback, Uri url, IAuthInfo credentials, KeyValuePair<string, string> upload, Uri file, IDictionary<string, string> data = null, UploadProgress progress = null)
        {
            return Execute(delegate
            {
                HttpWebRequest request = CreateRequest(url, credentials, METHOD.POST);

                string boundary = "---------------------" + DateTime.Now.Ticks.ToString("x");

                byte[] boundarybytes = Encoding.ASCII.GetBytes("--" + boundary + "\r\n");

                string header = string.Format("Content-Disposition: form-data; name=\"{0}\";filename=\"{1}\"\r\n Content-Type: image/jpeg\r\n\r\n", upload.Key, upload.Value);

                byte[] headerbytes = Encoding.UTF8.GetBytes(header);

                int length = 0;

                MemoryStream memory = new MemoryStream();

                if (data != null)
                {
                    foreach (var pair in data)
                    {
                        byte[] param = Encoding.UTF8.GetBytes("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + pair.Key + "\"\r\n\r\n" + pair.Value + "\r\n");
                        memory.Write(param, 0, param.Length);
                        length += param.Length;
                    }
                }

                byte[] buffer = new byte[BUFF_SIZE];

                int bytesRead = 0;

                memory.Write(boundarybytes, 0, boundarybytes.Length);
                length += boundarybytes.Length;

                memory.Write(headerbytes, 0, headerbytes.Length);
                length += headerbytes.Length;

                FileStream fileStream = null;

                try
                {
                    fileStream = new FileStream(file.AbsolutePath.DecodeUrl(), FileMode.Open, FileAccess.Read);
                }
                catch
                {
                    callback(new BlipError("Plik nie został odnaleziony"));
                    return;
                }

                while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                {
                    memory.Write(buffer, 0, bytesRead);
                    length += bytesRead;
                }

                boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
                memory.Write(boundarybytes, 0, boundarybytes.Length);
                length += boundarybytes.Length;

                request.ContentType = "multipart/form-data; boundary=" + boundary;
                request.KeepAlive = true;
                request.ContentLength = length;

                Stream stream = request.GetRequestStream();
                memory.Seek(0, SeekOrigin.Begin);
                double allRead = 0;
                double factor = 100.0 / memory.Length;
                while ((bytesRead = memory.Read(buffer, 0, buffer.Length)) > 0)
                {
                    stream.Write(buffer, 0, bytesRead);
                    if (progress != null)
                    {
                        allRead += bytesRead;
                        progress(allRead * factor);
                    }
                }
                stream.Close();
                fileStream.Close();
                memory.Close();

                ReadResponse(request, callback);
            });
        }
    }
}